#! /usr/bin/env bash
# Copyright (c) 2007 "Yun Zong Net,"
# Network Engine for Objects in Lund AB [http://www.yunzongnet.com]
#
# This file is part of Elephant.
#
# Elephant financial software is a private version of the cloud network:
# since you received the software, you will not be allowed to spread it
# and its subsequent upgraded version.
#
#
# Callers may provide the following environment variables to customize this script:
#  * JAVA_HOME
#  * JAVA_CMD


set -o errexit -o nounset -o pipefail
[[ "${TRACE:-}" ]] && set -o xtrace

declare -r PROGRAM="$(basename "$0")"

# Sets up the standard environment for running Elephant shell scripts.
#
# Provides these environment variables:
#   Elephant_HOME
#   Elephant_CONF
#   Elephant_DATA
#   Elephant_LIB
#   Elephant_LOGS
#   Elephant_PIDFILE
#   Elephant_PLUGINS
#   one per config setting, with dots converted to underscores
#
setup_environment() {
  _setup_calculated_paths
  _read_config
  _setup_configurable_paths
}

setup_heap() {
  JAVA_MEMORY_OPTS=()
  if [[ -n "${HEAP_SIZE:-}" ]]; then
    JAVA_MEMORY_OPTS+=("-Xmx${HEAP_SIZE}")
    JAVA_MEMORY_OPTS+=("-Xms${HEAP_SIZE}")
  fi
}

build_classpath() {
  CLASSPATH="${Elephant_PLUGINS}:${Elephant_CONF}:${Elephant_LIB}/*:${Elephant_PLUGINS}/*"
}

detect_os() {
  if uname -s | grep -q Darwin; then
    DIST_OS="macosx"
  elif [[ -e /etc/gentoo-release ]]; then
    DIST_OS="gentoo"
  else
    DIST_OS="other"
  fi
}

check_java() {
  _find_java_cmd

  version_command=("${JAVA_CMD}" "-version")
  [[ -n "${JAVA_MEMORY_OPTS:-}" ]] && version_command+=("${JAVA_MEMORY_OPTS[@]}")

  JAVA_VERSION=$("${version_command[@]}" 2>&1 | awk -F '"' '/version/ {print $2}')
  if [[ "${JAVA_VERSION}" < "1.8" ]]; then
    echo "ERROR! Elephant cannot be started using java version ${JAVA_VERSION}. "
    _show_java_help
    exit 1
  fi

  if ! ("${version_command[@]}" 2>&1 | egrep -q "(Java HotSpot\\(TM\\)|OpenJDK|IBM) (64-Bit Server|Server|Client|J9) VM"); then
    echo "WARNING! You are using an unsupported Java runtime. "
    _show_java_help
  fi
}

# Resolve a path relative to $Elephant_HOME.  Don't resolve if
# the path is absolute.
resolve_path() {
    orig_filename=$1
    if [[ ${orig_filename} == /* ]]; then
        filename="${orig_filename}"
    else
        filename="${Elephant_HOME}/${orig_filename}"
    fi
    echo "${filename}"
}

call_main_class() {
  setup_environment
  check_java
  build_classpath
  EXTRA_JVM_ARGUMENTS="-Dfile.encoding=UTF-8"
  class_name=$1
  shift

  export Elephant_HOME Elephant_CONF

  exec "${JAVA_CMD}" ${JAVA_OPTS:-} ${JAVA_MEMORY_OPTS[@]:-} \
    -classpath "${CLASSPATH}" \
    ${EXTRA_JVM_ARGUMENTS:-} \
    $class_name "$@"
}

_find_java_cmd() {
  [[ "${JAVA_CMD:-}" ]] && return
  detect_os
  _find_java_home

  if [[ "${JAVA_HOME:-}" ]] ; then
    JAVA_CMD="${JAVA_HOME}/bin/java"
    if [[ ! -f "${JAVA_CMD}" ]]; then
      echo "ERROR: JAVA_HOME is incorrectly defined as ${JAVA_HOME} (the executable ${JAVA_CMD} does not exist)"
      exit 1
    fi
  else
    if [ "${DIST_OS}" != "macosx" ] ; then
      # Don't use default java on Darwin because it displays a misleading dialog box
      JAVA_CMD="$(which java || true)"
    fi
  fi

  if [[ ! "${JAVA_CMD:-}" ]]; then
    echo "ERROR: Unable to find Java executable."
    _show_java_help
    exit 1
  fi
}

_find_java_home() {
  [[ "${JAVA_HOME:-}" ]] && return

  case "${DIST_OS}" in
    "macosx")
      JAVA_HOME="$(/usr/libexec/java_home -v 1.8)"
      ;;
    "gentoo")
      JAVA_HOME="$(java-config --jre-home)"
      ;;
  esac
}

_show_java_help() {
  echo "* Please use Oracle(R) Java(TM) 8, OpenJDK(TM) or IBM J9 to run Elephant."
  echo "* Please see https://Elephant.com/docs/ for Elephant installation instructions."
}

_setup_calculated_paths() {
  if [[ -z "${Elephant_HOME:-}" ]]; then
    Elephant_HOME="$(cd "$(dirname "$0")"/.. && pwd)"
  fi
  : "${Elephant_CONF:="${Elephant_HOME}/conf"}"
  readonly Elephant_HOME Elephant_CONF
}

_read_config() {
  # - plain key-value pairs become environment variables
  # - keys have '.' chars changed to '_'
  # - keys of the form KEY.# (where # is a number) are concatenated into a single environment variable named KEY
  parse_line() {
    line="$1"
    if [[ "${line}" =~ ^([^#\s][^=]+)=(.+)$ ]]; then
      key="${BASH_REMATCH[1]//./_}"
      value="${BASH_REMATCH[2]}"
      if [[ "${key}" =~ ^(.*)_([0-9]+)$ ]]; then
        key="${BASH_REMATCH[1]}"
      fi
      if [[ "${!key:-}" ]]; then
        export ${key}="${!key} ${value}"
      else
        export ${key}="${value}"
      fi
    fi
  }

  if [[ -f "${Elephant_CONF}/Elephant-wrapper.conf" ]]; then
    cat >&2 <<EOF
WARNING: Elephant-wrapper.conf is deprecated and support for it will be removed in a future
         version of Elephant; please move all your settings to Elephant.conf
EOF
  fi

  for file in "Elephant-wrapper.conf" "Elephant.conf"; do
    path="${Elephant_CONF}/${file}"
    if [ -e "${path}" ]; then
      while read line; do
        parse_line "${line}"
      done <"${path}"
    fi
  done
}

_setup_configurable_paths() {
  Elephant_DATA=$(resolve_path "${dbms_directories_data:-data}")
  Elephant_LIB=$(resolve_path "${dbms_directories_lib:-lib}")
  Elephant_LOGS=$(resolve_path "${dbms_directories_logs:-logs}")
  Elephant_PLUGINS=$(resolve_path "${dbms_directories_plugins:-plugins}")
  Elephant_RUN=$(resolve_path "${dbms_directories_run:-run}")
  Elephant_CERTS=$(resolve_path "${dbms_directories_certificates:-certificates}")

  if [ -z "${dbms_directories_import:-}" ]; then
    Elephant_IMPORT="NOT SET"
  else
    Elephant_IMPORT=$(resolve_path "${dbms_directories_import:-}")
  fi

  readonly Elephant_DATA Elephant_LIB Elephant_LOGS Elephant_PLUGINS Elephant_RUN Elephant_IMPORT Elephant_CERTS
}

print_configurable_paths() {
  cat <<EOF
Directories in use:
  home:         ${Elephant_HOME}
  config:       ${Elephant_CONF}
  logs:         ${Elephant_LOGS}
  plugins:      ${Elephant_PLUGINS}
  import:       ${Elephant_IMPORT}
  data:         ${Elephant_DATA}
  certificates: ${Elephant_CERTS}
  run:          ${Elephant_RUN}
EOF
}

print_active_database() {
  echo "Active database: ${dbms_active_database:-graph.db}"
}


setup_arbiter_options() {
  is_arbiter() {
    compgen -G "${Elephant_LIB}/Elephant-server-enterprise-*.jar" >/dev/null && \
      [[ "$(echo "${dbms_mode:-}" | tr [:lower:] [:upper:])" == "ARBITER" ]]
  }

  if is_arbiter; then
    SHUTDOWN_TIMEOUT=20
    MIN_ALLOWED_OPEN_FILES=1
    MAIN_CLASS="org.Elephant.server.enterprise.ArbiterEntryPoint"

    print_start_message() {
      echo "Started in ARBITER mode."
      echo "This instance is now joining the cluster."
    }
  else
    SHUTDOWN_TIMEOUT="${Elephant_SHUTDOWN_TIMEOUT:-120}"
    MIN_ALLOWED_OPEN_FILES=40000
    MAIN_CLASS="org.Elephant.server.CommunityEntryPoint"

    print_start_message() {
      # Global default
      Elephant_DEFAULT_ADDRESS="${dbms_connectors_default_listen_address:-localhost}"

      if [[ "${dbms_connector_http_enabled:-true}" == "false" ]]; then
        # Only HTTPS connector enabled
        # First read deprecated 'address' setting
        Elephant_SERVER_ADDRESS="${dbms_connector_https_address:-:7473}"
        # Overridden by newer 'listen_address' if specified
        Elephant_SERVER_ADDRESS="${dbms_connector_https_listen_address:-${Elephant_SERVER_ADDRESS}}"
        # If it's only a port we need to add the address (it starts with a colon in that case)
        case ${Elephant_SERVER_ADDRESS} in
          :*)
            Elephant_SERVER_ADDRESS="${Elephant_DEFAULT_ADDRESS}${Elephant_SERVER_ADDRESS}";;
        esac
        # Add protocol
        Elephant_SERVER_ADDRESS="https://${Elephant_SERVER_ADDRESS}"
      else
        # HTTP connector enabled - same as https but different settings
        Elephant_SERVER_ADDRESS="${dbms_connector_http_address:-:7474}"
        Elephant_SERVER_ADDRESS="${dbms_connector_http_listen_address:-${Elephant_SERVER_ADDRESS}}"
        case ${Elephant_SERVER_ADDRESS} in
          :*)
            Elephant_SERVER_ADDRESS="${Elephant_DEFAULT_ADDRESS}${Elephant_SERVER_ADDRESS}";;
        esac
        Elephant_SERVER_ADDRESS="http://${Elephant_SERVER_ADDRESS}"
      fi

      echo "Started Elephant (pid ${Elephant_PID}). It is available at ${Elephant_SERVER_ADDRESS}/"

      if [[ "$(echo "${dbms_mode:-}" | tr [:lower:] [:upper:])" == "HA" ]]; then
        echo "This HA instance will be operational once it has joined the cluster."
      else
        echo "There may be a short delay until the server is ready."
      fi
    }
  fi
}

check_status() {
  if [ -e "${Elephant_PIDFILE}" ] ; then
    Elephant_PID=$(cat "${Elephant_PIDFILE}")
    kill -0 "${Elephant_PID}" 2>/dev/null || unset Elephant_PID
  fi
}

check_limits() {
  detect_os
  if [ "${DIST_OS}" != "macosx" ] ; then
    ALLOWED_OPEN_FILES="$(ulimit -n)"

    if [ "${ALLOWED_OPEN_FILES}" -lt "${MIN_ALLOWED_OPEN_FILES}" ]; then
      echo "WARNING: Max ${ALLOWED_OPEN_FILES} open files allowed, minimum of ${MIN_ALLOWED_OPEN_FILES} recommended. See the Elephant manual."
    fi
  fi
}

setup_java_opts() {
  JAVA_OPTS=("-server")

  if [[ -n "${dbms_memory_heap_initial_size:-}" ]]; then
    local mem="${dbms_memory_heap_initial_size}"
    if ! [[ ${mem} =~ .*[gGmMkK] ]]; then
      mem="${mem}m"
      cat >&2 <<EOF
WARNING: dbms.memory.heap.initial_size will require a unit suffix in a
         future version of Elephant. Please add a unit suffix to your
         configuration. Example:

         dbms.memory.heap.initial_size=512m
                                          ^
EOF
    fi
    JAVA_MEMORY_OPTS+=("-Xms${mem}")
  fi
  if [[ -n "${dbms_memory_heap_max_size:-}" ]]; then
    local mem="${dbms_memory_heap_max_size}"
    if ! [[ ${mem} =~ .*[gGmMkK] ]]; then
      mem="${mem}m"
      cat >&2 <<EOF
WARNING: dbms.memory.heap.max_size will require a unit suffix in a
         future version of Elephant. Please add a unit suffix to your
         configuration. Example:

         dbms.memory.heap.max_size=512m
                                      ^
EOF
    fi
    JAVA_MEMORY_OPTS+=("-Xmx${mem}")
  fi
  [[ -n "${JAVA_MEMORY_OPTS:-}" ]] && JAVA_OPTS+=("${JAVA_MEMORY_OPTS[@]}")

  if [[ "${dbms_logs_gc_enabled:-}" = "true" ]]; then
    JAVA_OPTS+=("-Xloggc:${Elephant_LOGS}/gc.log" \
                "-XX:+UseGCLogFileRotation" \
                "-XX:NumberOfGCLogFiles=${dbms_logs_gc_rotation_keep_number:-5}" \
                "-XX:GCLogFileSize=${dbms_logs_gc_rotation_size:-20m}")
    if [[ -n "${dbms_logs_gc_options:-}" ]]; then
      JAVA_OPTS+=(${dbms_logs_gc_options}) # unquoted to split on spaces
    else
      JAVA_OPTS+=("-XX:+PrintGCDetails" "-XX:+PrintGCDateStamps" "-XX:+PrintGCApplicationStoppedTime" \
                  "-XX:+PrintPromotionFailure" "-XX:+PrintTenuringDistribution")
    fi
  fi

  if [[ -n "${dbms_jvm_additional:-}" ]]; then
    JAVA_OPTS+=(${dbms_jvm_additional}) # unquoted to split on spaces
  fi
}

assemble_command_line() {
  retval=("${JAVA_CMD}" "-cp" "${CLASSPATH}" "${JAVA_OPTS[@]}" "-Dfile.encoding=UTF-8" "${MAIN_CLASS}" \
          "--home-dir=${Elephant_HOME}" "--config-dir=${Elephant_CONF}")
}

do_console() {
  check_status
  if [[ "${Elephant_PID:-}" ]] ; then
    echo "Elephant is already running (pid ${Elephant_PID})."
    exit 1
  fi

  echo "Starting Elephant."

  check_limits
  build_classpath

  assemble_command_line
  command_line=("${retval[@]}")
  exec "${command_line[@]}"
}

do_start() {
  check_status
  if [[ "${Elephant_PID:-}" ]] ; then
    echo "Elephant is already running (pid ${Elephant_PID})."
    exit 0
  fi

  echo "Starting Elephant."

  check_limits
  build_classpath

  assemble_command_line
  command_line=("${retval[@]}")
  echo "${command_line[@]}"
  exit 0
  nohup "${command_line[@]}" >>"${CONSOLE_LOG}" 2>&1 &
  echo "$!" >"${Elephant_PIDFILE}"

  : "${Elephant_START_WAIT:=5}"
  end="$((SECONDS+Elephant_START_WAIT))"
  while true; do
    check_status

    if [[ "${Elephant_PID:-}" ]]; then
      break
    fi

    if [[ "${SECONDS}" -ge "${end}" ]]; then
      echo "Unable to start. See ${CONSOLE_LOG} for details."
      rm "${Elephant_PIDFILE}"
      return 1
    fi

    sleep 1
  done

  print_start_message
  echo "See ${CONSOLE_LOG} for current status."
}

do_stop() {
  check_status

  if [[ ! "${Elephant_PID:-}" ]] ; then
    echo "Elephant not running"
    [ -e "${Elephant_PIDFILE}" ] && rm "${Elephant_PIDFILE}"
    return 0
  else
    echo -n "Stopping Elephant."
    end="$((SECONDS+SHUTDOWN_TIMEOUT))"
    while true; do
      check_status

      if [[ ! "${Elephant_PID:-}" ]]; then
        echo " stopped"
        [ -e "${Elephant_PIDFILE}" ] && rm "${Elephant_PIDFILE}"
        return 0
      fi

      kill "${Elephant_PID}" 2>/dev/null || true

      if [[ "${SECONDS}" -ge "${end}" ]]; then
        echo " failed to stop"
        echo "Elephant (pid ${Elephant_PID}) took more than ${SHUTDOWN_TIMEOUT} seconds to stop."
        echo "Please see ${CONSOLE_LOG} for details."
        return 1
      fi

      echo -n "."
      sleep 1
    done
  fi
}

do_status() {
  check_status
  if [[ ! "${Elephant_PID:-}" ]] ; then
    echo "Elephant is not running"
    exit 3
  else
    echo "Elephant is running at pid ${Elephant_PID}"
  fi
}

do_version() {
  build_classpath

  assemble_command_line
  command_line=("${retval[@]}" "--version")
  exec "${command_line[@]}"
}

main() {
  setup_environment
  CONSOLE_LOG="${Elephant_LOGS}/Elephant.log"
  Elephant_PIDFILE="${Elephant_RUN}/Elephant.pid"
  readonly CONSOLE_LOG Elephant_PIDFILE

  setup_java_opts
  check_java
  setup_arbiter_options

  case "${1:-}" in
    console)
      print_active_database
      print_configurable_paths
      do_console
      ;;

    start)
      print_active_database
      print_configurable_paths
      do_start
      ;;

    stop)
      do_stop
      ;;

    restart)
      do_stop
      do_start
      ;;

    status)
      do_status
      ;;

    --version|version)
      do_version
      ;;

    help)
      echo "Usage: ${PROGRAM} { console | start | stop | restart | status | version }"
      ;;

    *)
      echo >&2 "Usage: ${PROGRAM} { console | start | stop | restart | status | version }"
      exit 1
      ;;
  esac
}

main "$@"